﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Nes.Core.APUS
{
    /// <summary>
    /// 方波
    /// </summary>
    public class Pulse
    {
        public Pulse(int channel)
        {
            this.channel = channel;
        }

        public Int32 LengthCounter { get; internal set; }

        private Boolean _enable;
        public bool Enable 
        { get 
            {
                return _enable;
            }
            set 
            {
                _enable = value;
                if (!Enable)
                {
                    LengthCounter = 0;
                }
            } 
        }
        public int Volume { get; internal set; }

        private Int32 channel;

        private Int32 Duty;
        private Boolean IsEnvelopeLoop;
        private Boolean IsConstantVolume;

        private Int32 EnvelopeValue;
        private Int32 EnvelopeVolume;
        private Int32 EnvelopeCounter;
        private Int32 Counter;

        private Boolean IsSweepEnable;
        private Boolean IsSweepNegated;
        private Int32 SweepPeriod;
        private Int32 SweepShift;
        private Int32 SweepCounter;
        private Int32 Timer;
        private Int32 InternalTimer;


        internal void Clock()
        {
            if (!Enable)
            {
                return;
            }
            if(InternalTimer == 0)
            {
                InternalTimer = Timer;
                Step();
            }
            else
            {
                InternalTimer--;
            }
        }

        private void Step()
        {
            Counter++;
            if(!Enable || LengthCounter ==0 || Timer < 8 || Timer > 0x7FF)
            {
                Volume = 0;
            }
            else if (IsConstantVolume)
            {
                Volume = EnvelopeValue * CommonProp.DutyTable[Duty][Counter & 0x07];
            }
            else
            {
                Volume = EnvelopeVolume * CommonProp.DutyTable[Duty][Counter & 0x07];
            }
        }

        internal void ProcessEnvelope()
        {
            if (IsConstantVolume)
            {
                return;
            }
            if(EnvelopeCounter % (EnvelopeValue + 1) == 0)
            {
                if(EnvelopeVolume == 0)
                {
                    EnvelopeVolume = IsEnvelopeLoop ? 15 : 0;
                }
                else
                {
                    EnvelopeVolume--;
                }
            }
            EnvelopeCounter++;
        }

        internal void ProcessLengthCounter()
        {
            if(!IsEnvelopeLoop && LengthCounter > 0)
            {
                LengthCounter--;
            }
        }

        internal void ProcessSweep()
        {
            if (!IsSweepEnable)
            {
                return;
            }
            if(SweepCounter % (SweepPeriod + 1) == 0)
            {
                var changeAmount = (IsSweepNegated ? -1 : 1) * (Timer >> SweepShift);
                Timer += changeAmount;
                if(channel == 1 && changeAmount < 1)
                {
                    Timer--;
                }
            }
            SweepCounter++;
        }

        internal void Write(Int32 addr, Byte data)
        {
            switch (addr)
            {
                case 0: // DDLC VVVV
                    Duty = data >> 6;
                    //  length counter halt
                    IsEnvelopeLoop = (data & 0b0010_0000) > 0;
                    // C
                    IsConstantVolume = (data & 0b0001_0000) > 0;
                    EnvelopeValue = (data & 0b0000_1111);
                    EnvelopeVolume = 15;
                    EnvelopeCounter = 0;
                    break;
                case 1: // EPPP NSSS
                    IsSweepEnable = (data & 0b1000_0000) > 0;
                    SweepPeriod = (data & 0b0111_0000) >> 4;
                    IsSweepNegated = (data & 0b0000_1000) > 0;
                    SweepShift = (data & 0b0000_0111);
                    SweepCounter = 0;
                    break;
                case 2: // LLLL LLLL
                    Timer = ((Timer & 0xFF00) | data);
                    break;
                case 3: // llll lHHH
                    Timer = (Timer & 0x00FF) | (data & 0b0000_0111) << 8;
                    LengthCounter = CommonProp.LengthTable[data >> 3];
                    InternalTimer = 0;
                    break;
            }
        }
    }
}
